<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <style>
        body {
            margin: 10px;
        }

        .toolbar {
            background: #ddd;
            height: 40px;
            line-height: 40px;
            padding: 0 4px;
        }
        .editor {
            min-height: 200px;
            border: 4px solid #ddd;
            font-size: 2rem;
            font-family: Cambria, Cochin, Georgia, Times, 'Times New Roman', serif;
        }
    </style>
</head>

<body>
    <div class="toolbar">
        <button data-cmd="Bold">B</button>
        <button data-cmd="Italic">I</button>
        <button data-cmd="UnderLine">U</button>
        <button data-cmd="HighLight">H</button>
        <button data-cmd="undo">undo</button>
        <span>|</span>
        <button data-cmd="Save" id="save">Save</button>
    </div>
    <div class="editor" contenteditable="true">
        Lorem ipsum dolor sit, amet consectetur adipisicing elit. Vero nulla minima similique. Accusamus alias dolore
        cumque explicabo, nostrum quisquam rerum quaerat provident voluptatibus illo error earum sed nisi vel! Illum.
    </div>

    <div class="snapshots">

    </div>

    <script>
        class Command {
            constructor(editor) {
                this.editor = editor;
                this.state = "";
            }
            Execute() {
                this.state = this.editor.getHTML();
                this.exec();
            }
            undo() {
                this.editor.setHTML(this.state);
            }
            getState() {
                return this.state;
            }

        }
        class BoldCommand extends Command {
            exec() {
                this.editor.Bold();
            }
        }
        class ItalicCommand extends Command {
            exec() {
                this.editor.Italic();
            }
        }
        class UnderLineCommand extends Command {
            exec() {
                this.editor.UnderLine();
            }
        }
        class HighLightCommand extends Command {
            exec() {
                this.editor.HighLight();
            }
        }
        class SaveCommand extends Command{
            exec(){
                alert("Saved!!!!!")
            }
        }

        class Editor {
            constructor(el) {
                this.el = el;
            }
            wrapElement(nodeName, style) {
                const selec = window.getSelection();
                const focusEle = selec.focusNode.nodeType === Node.TEXT_NODE ?
                    selec.focusNode.parentElement : selec.focusNode;

                if (focusEle.nodeName === nodeName.toUpperCase()) {
                    focusEle.outerHTML = focusEle.innerHTML;
                } else {
                    const range = selec.getRangeAt(0);
                    const node = document.createElement(nodeName);
                    style && (node.style = style)
                    range.surroundContents(node);

                    const newRange = document.createRange();
                    newRange.selectNodeContents(node);

                    selec.removeAllRanges();
                    selec.addRange(newRange);
                }
            }

            Bold() {
                this.wrapElement("b");
            }
            Italic() {
                this.wrapElement("i");
            }
            UnderLine() {
                this.wrapElement("u");
            }
            HighLight() {
                this.wrapElement("span", "background:yellow;")
            }

            getHTML() {
                return this.el.innerHTML;
            }
            setHTML(value) {
                this.el.innerHTML = value;
            }
        }

        class CommandExector {
            constructor() {
                this.factory = {};
                this.history = [];
            }
            registerCommmand(name, func) {
                this.factory[name] = func;
            }
            exec(name) {
                const cmd = this.factory[name]();
                cmd.Execute();
                this.history.push(cmd);
            }
            undo() {
                const popCmd = this.history.pop();
                if (!popCmd) return;

                popCmd.undo();
            }
            createSnapshot(){
                return new Snapshot([...this.history])
            }
            restorySnapshot(snapshot){
                const state = snapshot.getState();
                this.history = [...state];
                this.undo();
            }
        }

        class Snapshot{
            constructor(state){
                this.state = state;
            }
            getState(){
                return this.state;
            }
        }
        class SnapshotHistory{
            constructor(render){
                this.history = [];
                this.render = render;
            }
            addSnapshot(snapshot){
                this.history.push(snapshot);
                this.render.render(this);
            }
            get(index){
                return this.history[index];
            }
        }
        class SnapshotRender{
            constructor(el,common){
                this.el = el;
                this.common = common;
            }
            render(history){
                this.el.innerHTML = "";
                history.history.forEach((item,idx)=>{
                    const div = document.createElement("div");
                    div.innerHTML = `#${idx} snapshot`;
                    div.dataset.idx = idx;
                    this.el.append(div);

                    div.addEventListener("click",e=>{
                        const idx = e.target.dataset.idx;
                        const state = history.get(idx);
                        this.common.restorySnapshot(state);
                    })

                })
            }
        }
       
        const editor = new Editor(document.querySelector(".editor"));
        const commexec = new CommandExector();
        commexec.registerCommmand("Bold", () => {
            return new BoldCommand(editor)
        })
        commexec.registerCommmand("Italic", () => {
            return new ItalicCommand(editor)
        })
        commexec.registerCommmand("UnderLine", () => {
            return new UnderLineCommand(editor)
        })
        commexec.registerCommmand("HighLight", () => {
            return new HighLightCommand(editor)
        })
        commexec.registerCommmand("Save",()=>{
            return new SaveCommand(editor)
        })

        document.querySelectorAll("button").forEach(item => {
            item.addEventListener("click", e => {
                const cmd = e.target.dataset.cmd;
                if (cmd === "undo") {
                    commexec.undo();
                } else {
                    commexec.exec(cmd);
                }
            })
        })
        const render = new SnapshotRender(document.querySelector(".snapshots"),
        commexec);
        const snapshotHistory = new SnapshotHistory(render);

        document.querySelector("#save").addEventListener("click",e=>{
            const snapshot = commexec.createSnapshot();
            snapshotHistory.addSnapshot(snapshot);

        })
       

    </script>
</body>
</html>